<?php

namespace backend\components\adapter;

use Yii;
use yii\helpers\ArrayHelper;
use yii\helpers\FileHelper;
use backend\models\Clients;
use backend\models\Extensions;
use backend\components\Installer;
use backend\components\InstallerAdapter;

/**
 * Package installer
 *
 * @author loong
 */
class PackageAdapter extends InstallerAdapter
{
    /**
     * @var array The results of each installed extensions
     */
    protected $results = [];

    /**
     * Method to do any prechecks and setup the install paths for the extension
     * @return void
     */
    protected function setupInstallPaths()
    {
        $packagePath = (string)$this->manifest->packagename;
        if (empty($packagePath)) {
            $msg = Yii::t('installer', 'ABORT_PACK_INSTALL_NO_PACK', $this->route);
            throw new \RuntimeException($msg);
        }
        $this->parent->setPath('extension_root', Yii::getAlias('@backend/manifests/packages/' . $packagePath));
    }

    /**
     * {@inheritdoc}
     */
    protected function checkExtensionInFilesystem()
    {
        if (is_file(Yii::getAlias('@backend/manifests/packages/' . basename($this->parent->manifestFile)))) {
            $updateElement = $this->manifest->update;
            if (($updateElement || $this->parent->isUpgrade()) && $this->currentExtensionId) {
                $this->set('route', 'update');
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function createExtensionRoot()
    {
        /**
         * For packages, we only need the extension root if copying manifest files; this step will be handled
         * at that point if necessary
         */
    }

    /**
     * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file
     * @return void
     * @throws  \RuntimeException
     * @throws yii\base\ErrorException
     * @throws yii\base\Exception
     */
    protected function copyBaseFiles()
    {
        // Install all necessary files
        if (!count($this->manifest->files->children())) {
            $msg = Yii::t('installer', 'ABORT_PACK_INSTALL_NO_FILES', $this->route);
            throw new \yii\base\Exception($msg);
        }

        $source = $this->parent->source;
        foreach ($this->manifest->files->children() as $child) {
            $file = $source . '/' . (string)$child;
            if (is_dir($file)) {
                $package = [];
                $package['dir'] = $file;
                $package['type'] = $this->parent->detectType($file);
            } else {
                $package = $this->parent->unpack($file);
            }
            $tmpInstaller = new Installer();
            $installResult = $tmpInstaller->install($package['dir']);
            if (!$installResult) {
                $msg = Yii::t('installer', 'ABORT_PACK_INSTALL_ERROR_EXTENSION', [
                    'route' => Yii::t('installer', strtoupper($this->route)),
                    'file' => basename($file)
                ]);
                throw new \yii\base\Exception($msg);
            }
            $this->results[] = [
                'name' => (string)$tmpInstaller->manifest->name,
                'result' => $installResult
            ];
        }
    }

    /**
     * Method to finalise the installation processing
     */
    protected function finaliseInstall()
    {
        if (!empty($this->results)) {
            $ids = ArrayHelper::getColumn($this->results, 'result');
            try {
                Extensions::updateAll(['package_id' => $this->extension->id], ['id' => $ids]);
            } catch (\yii\db\Exception $e) {
                $errors = Yii::t('installer', 'ERROR_PACK_SETTING_PACKAGE_ID');
                Yii::$app->session->addFlash('error', $errors);
            }

        }
        $src = $this->parent->manifestFile;
        $dest = Yii::getAlias('@backend/manifests/packages/' . basename($src));
        copy($src, $dest);
    }

    /**
     * Method to store the extension to the database
     * @return void
     * @throws \RuntimeException
     */
    protected function storeExtension()
    {
        if ($this->currentExtensionId) {
            $this->currentExtensionId->name = $this->name;
            $this->extension = $this->currentExtensionId;
        } else {
            $this->extension->name = $this->name;
            $this->extension->type = 'package';
            $this->extension->element = $this->getElement();
            $this->extension->folder = '';
            $this->extension->client_id = 0;
            $this->extension->enabled = 1;
            $this->extension->protected = 0;
            $this->extension->params = $this->parent->getParams();
            $this->extension->status = 1;
        }
        $this->extension->manifest_cache = $this->parent->generateManifestCache();
        if (!$this->extension->save()) {
            $errors = $this->parent->errorsToString($this->extension->getErrors());
            $msg = Yii::t('installer', 'ABORT_PACK_INSTALL_ROLLBACK', $errors);
            throw new \RuntimeException($msg);
        }
        $this->parent->pushStep(['type' => 'extension', 'id' => $this->extension->id]);
    }

    /**
     * Get the filtered extension element from the manifest
     * @param string $element Optional element name to be converted
     * @return string The filtered element
     */
    public function getElement($element = null)
    {
        if (!$element) {
            $element = (string)$this->manifest->packagename;
            $element = 'pkg_' . trim($element);
        }
        return $element;
    }

    public function uninstall($extension)
    {
        if ($extension->protected === 1) {
            Yii::$app->session->addFlash('warning', Yii::t('installer', 'ERROR_PACK_UNINSTALL_WARN_CORE_PACK'));
            return false;
        }

        /*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
		 */
        if ($extension->package_id && !$this->parent->isPackageUninstall()) {
            Yii::$app->session->addFlash('warning', Yii::t('installer', 'ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $extension->name));
            return false;
        }

        $manifestFile = Yii::getAlias('@backend/manifests/packages/' . $extension->element . '.xml');
        if (!is_file($manifestFile)) {
            Yii::$app->session->addFlash('warning', Yii::t('installer', 'ERROR_PACK_UNINSTALL_MISSING_MANIFEST'));
            return false;
        }

        $xml = simplexml_load_file($manifestFile);
        if (!$xml) {
            Yii::$app->session->addFlash('warning', Yii::t('installer', 'ERROR_PACK_UNINSTALL_LOAD_MANIFEST'));
            return false;
        }

        if ($xml->getName() !== 'extension') {
            Yii::$app->session->addFlash('warning', Yii::t('installer', 'ERROR_PACK_UNINSTALL_INVALID_MANIFEST'));
            return false;
        }

        $this->parent->setPath('extension_root', Yii::getAlias('@backend/manifests/packages/' . (string)$xml->packagename));

        $error = false;
        if (!empty($xml->files->folder)) {
            foreach ($xml->files->folder as $folder) {
                $tmpInstaller = new Installer();
                $tmpInstaller->setPackageUninstall(true);
                $folderType = (string)$folder->attributes()->type;
                $folderId = (string)$folder->attributes()->id;
                $folderClient = (string)$folder->attributes()->client;
                $folderGroup = (string)$folder->attributes()->group;
                $id = $this->_getExtensionId($folderType, $folderId, $folderClient, $folderGroup);
                if ($id) {
                    if (!$tmpInstaller->uninstall([$id])) {
                        $error = true;
                        Yii::$app->session->addFlash('warning', Yii::t('installer', 'ERROR_PACK_UNINSTALL_NOT_PROPER', (string)$folder));
                    }
                } else {
                    Yii::$app->session->addFlash('warning', Yii::t('installer', 'ERROR_PACK_UNINSTALL_UNKNOWN_EXTENSION'));
                }
            }
        }
        if (!empty($xml->files->file)) {
            foreach ($xml->files->file as $file) {
                $tmpInstaller = new Installer();
                $tmpInstaller->setPackageUninstall(true);
                $fileType = (string)$file->attributes()->type;
                $fileId = (string)$file->attributes()->id;
                $fileClient = (string)$file->attributes()->client;
                $fileGroup = (string)$file->attributes()->group;
                $id = $this->_getExtensionId($fileType, $fileId, $fileClient, $fileGroup);
                if ($id) {
                    if (!$tmpInstaller->uninstall([$id])) {
                        $error = true;
                        Yii::$app->session->addFlash('warning', Yii::t('installer', 'ERROR_PACK_UNINSTALL_NOT_PROPER', (string)$file));
                    }
                } else {
                    Yii::$app->session->addFlash('warning', Yii::t('installer', 'ERROR_PACK_UNINSTALL_UNKNOWN_EXTENSION'));
                }
            }
        }
        $this->parent->removeFiles($xml->backend->messages);

        if (!$error) {
            FileHelper::unlink($manifestFile);
            $folder = $this->parent->getPath('extension_root');
            if (is_dir($folder)) {
                FileHelper::removeDirectory($folder);
            }
            $extension->delete();
        } else {
            Yii::$app->session->addFlash('error', Yii::t('installer', 'ERROR_PACK_UNINSTALL_MANIFEST_NOT_REMOVED'));
            return false;
        }
        return true;
    }

    /**
     * 获取扩展id
     * @param string $type 扩展类型
     * @param string $id 扩展元素
     * @param string $client 应用名称
     * @param string $group 扩展分组
     * @return int|null
     */
    protected function _getExtensionId($type, $id, $client, $group)
    {
        $extensions = Extensions::find()->where(['element' => $id]);
        switch ($type) {
            case 'plugin':
                $extensions->andWhere(['folder' => $group]);
                break;
            case 'library':
            case 'package':
            case 'module':
                break;
            case 'language':
            case 'component':
            case 'template':
                $client = Clients::findOne(['name' => strtolower($client)]);
                $extensions->andWhere(['client_id' => $client->id]);
                break;
        }
        $extension = $extensions->one();
        return is_null($extension) ? null : $extension->id;
    }
}
